package org.kwin.spring.aop.aspectj.proxy;

import org.kwin.spring.aop.annotation.After;
import org.kwin.spring.aop.annotation.Around;
import org.kwin.spring.aop.annotation.Before;
import org.kwin.spring.aop.annotation.Throwing;

import java.lang.reflect.Method;

/**
 * 切面代理
 */
public class AspectProxy implements Proxy {

    private Object instance;
    
    public AspectProxy(Object instance) {
        this.instance = instance;
    }
    
    @Override
    public final Object doProxy(ProxyChain proxyChain) throws Throwable {
        Object result = null;

        Class<?> cls = proxyChain.getTargetClass();
        Method method = proxyChain.getTargetMethod();
        Object[] params = proxyChain.getMethodParams();

        try {
            if (intercept(method, params)) {
                before(params);
            }
            result = around(method, params, proxyChain);
            if (intercept(method, params)) {
                after(params);
            }
        } catch (Exception e) {
//            e.printStackTrace();
            if (intercept(method, params)) {
                throwing(params, e);
            }
            throw e;
        } finally {
        
        }

        return result;
    }
    

    /**
     * 切入点判断
     */
    public boolean intercept(Method method, Object[] params) throws Throwable {
        return true;
    }
    
    private void before( Object[] params) {
        Method[] methods = instance.getClass().getDeclaredMethods();
        for(Method method : methods) {
            if(method.isAnnotationPresent(Before.class)) {
                try {
                    method.setAccessible(true);
                    Class<?>[] clzzs = method.getParameterTypes();
                    if(clzzs == null || clzzs.length == 0) {
                        method.invoke(instance);
                    } else {
                        method.invoke(instance, (Object)params);
                    }
                    return;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private void after(Object[] params) {
        
        Method[] methods = instance.getClass().getDeclaredMethods();
        for(Method method : methods) {
            if(method.isAnnotationPresent(After.class)) {
                try {
                    method.setAccessible(true);
                    Class<?>[] clzzs = method.getParameterTypes();
                    if(clzzs == null || clzzs.length == 0) {
                        method.invoke(instance);
                    } else {
                        method.invoke(instance, (Object)params);
                    }
                    return;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private void throwing(Object[] params, Exception ex) {
        
        Method[] methods = instance.getClass().getDeclaredMethods();
        for(Method method : methods) {
            if(method.isAnnotationPresent(Throwing.class)) {
                try {
                    method.setAccessible(true);
                    // TODO:
//                    method.getParameterTypes()
                    Class<?>[] clzzs = method.getParameterTypes();
                    if(clzzs == null || clzzs.length == 0) {
                        method.invoke(instance);
                    } else {
                        method.invoke(instance, (Object)params, ex);
                    }
                    return;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private Object around(Method meth, Object[] params, ProxyChain proxyChain) throws Throwable {
        Object ret = null;
        if(!intercept(meth, params)) {
            return proxyChain.doProxyChain();
        }
        Method[] methods = instance.getClass().getDeclaredMethods();
        for(Method method : methods) {
            if(method.isAnnotationPresent(Around.class)) {
                try {
                    method.setAccessible(true);
                    Class<?>[] clzzs = method.getParameterTypes();
                    if(clzzs == null || clzzs.length == 0) {
                        ret = method.invoke(instance);
                    } else {
                        ret = method.invoke(instance, (Object)params, proxyChain);
                    }
                    return ret;
                } catch (Exception e) {
                    throw e;
                }
            }
        }
        
        return proxyChain.doProxyChain();
    }
    
}